/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package intervalMath;

import java.math.*;

/**
 *
 * @author Vera Morozova
 */
public class IntervalElementaryMath {
    static BigDecimal PI = new BigDecimal("3.14159265358979323846264338327950288419716939937510582097494459230781640628620"
            + "899862803482534211706798214808651328230664709384460955058223172535940812848111"
            + "745028410270193852110555964462294895493038196442881097566593344612847564823378"
            + "678316527120190914564856692346034861045432664821339360726024914127372458700660"
            + "631558817488152092096282925409171536436789259036001133053054882046652138414695"
            + "194151160943305727036575959195309218611738193261179310511854807446237996274956"
            + "735188575272489122793818301194912983367336244065664308602139494639522473719070"
            + "217986094370277053921717629317675238467481846766940513200056812714526356082778"
            + "577134275778960917363717872146844090122495343014654958537105079227968925892354"
            + "201995611212902196086403441815981362977477130996051870721134999999837297804995"
            + "105973173281609631859502445945534690830264252230825334468503526193118817101000"
            + "313783875288658753320838142061717766914730359825349042875546873115956286388235"
            + "378759375195778185778053217122680661300192787661119590921642019893809525720106"
            + "548586327886593615338182796823030195203530185296899577362259941389124972177528"
            + "347913151557485724245415069595082953311686172785588907509838175463746493931925"
            + "506040092770167113900984882401285836160356370766010471018194295559619894676783"
            + "744944825537977472684710404753464620804668425906949129331367702898915210475216"
            + "205696602405803815019351125338243003558764024749647326391419927260426992279678"
            + "235478163600934172164121992458631503028618297455570674983850549458858692699569"
            + "092721079750930295532116534498720275596023648066549911988183479775356636980742"
            + "654252786255181841757467289097777279380008164706001614524919217321721477235014");

    /**
     * Iterative analytical algorithm of a square root calculation
     */
    public static IntervalArithmetic sqrt(IntervalArithmetic a){
        RoundingMode forLeft = a.getProperLeftRoundingMode();
        RoundingMode forRight = a.getProperRightRoundingMode();
        if (!a.isProper()) {
            forLeft = a.getImproperLeftRoundingMode();
            forRight = a.getImproperRightRoundingMode();
        }
        BigDecimal XnLeft = ElementaryMath.sqrt(a.getLeftLimit(), a.getScale(), forLeft);
        BigDecimal XnRight = ElementaryMath.sqrt(a.getRightLimit(), a.getScale(), forRight);
        IntervalArithmetic interval = (IntervalArithmetic)a.clone();
        interval.setLimits(XnLeft,XnRight);
        return interval;
    }

    public static IntervalArithmetic sqrt(IntervalArithmetic a, int scale){
        RoundingMode forLeft = a.getProperLeftRoundingMode();
        RoundingMode forRight = a.getProperRightRoundingMode();
        if (!a.isProper()) {
            forLeft = a.getImproperLeftRoundingMode();
            forRight = a.getImproperRightRoundingMode();
        }
        BigDecimal XnLeft = ElementaryMath.sqrt(a.getLeftLimit(), scale, forLeft);
        BigDecimal XnRight = ElementaryMath.sqrt(a.getRightLimit(), scale, forRight);
        IntervalArithmetic interval = (IntervalArithmetic)a.clone();
        interval.setLimits(XnLeft,XnRight, scale);
        return interval;
    }

     /**
     * Algorithm of a exponenta calculation
     */
    public static IntervalArithmetic exp(IntervalArithmetic a){
        RoundingMode forLeft = a.getProperLeftRoundingMode();
        RoundingMode forRight = a.getProperRightRoundingMode();
        if (!a.isProper()) {
            forLeft = a.getImproperLeftRoundingMode();
            forRight = a.getImproperRightRoundingMode();
        }
        BigDecimal XnLeft = ElementaryMath.exp(a.getLeftLimit(), a.getScale(), forLeft);
        BigDecimal XnRight = ElementaryMath.exp(a.getRightLimit(), a.getScale(), forRight);
        IntervalArithmetic interval = (IntervalArithmetic)a.clone();
        interval.setLimits(XnLeft,XnRight);
        return interval;
    }
    public static IntervalArithmetic exp(IntervalArithmetic a, int scale){
        RoundingMode forLeft = a.getProperLeftRoundingMode();
        RoundingMode forRight = a.getProperRightRoundingMode();
        if (!a.isProper()) {
            forLeft = a.getImproperLeftRoundingMode();
            forRight = a.getImproperRightRoundingMode();
        }
        BigDecimal XnLeft = ElementaryMath.exp(a.getLeftLimit(), scale, forLeft);
        BigDecimal XnRight = ElementaryMath.exp(a.getRightLimit(), scale, forRight);
        IntervalArithmetic interval = (IntervalArithmetic)a.clone();
        interval.setLimits(XnLeft,XnRight, scale);
        return interval;
    }

    private static BigDecimal[] getSinMinMax(IntervalArithmetic a, int scale){
        BigDecimal min;
        BigDecimal max;
        RoundingMode forLeft = a.getProperLeftRoundingMode();
        RoundingMode forRight = a.getProperRightRoundingMode();
        if (!a.isProper()) {
            forLeft = a.getImproperLeftRoundingMode();
            forRight = a.getImproperRightRoundingMode();
        }
        BigDecimal two = new BigDecimal(2);
        BigDecimal pi = PI.setScale(scale, RoundingMode.DOWN);
        BigDecimal halfPi = pi.divide(two, scale);
        BigDecimal twoPi = pi.multiply(two);
        BigDecimal width = a.getRightLimit().subtract(a.getLeftLimit()).abs();
        double sinTemp = Math.sin(a.getLeftLimit().add(width.divide(two, scale)).doubleValue());
        boolean cond3 =  false;
        boolean cond4 = false;
        for(int k = (int)Math.ceil(a.getLeftLimit().divide(halfPi, scale).doubleValue());
            k <= a.getRightLimit().divide(halfPi, scale).doubleValue();
            k++){
            if((k - 1)%4 == 0) {
                    cond3 = true;
            }
            if((k + 1)%4 == 0) {
                    cond4 = true;
            }
            if (cond3 && cond4) break;
        }
        double eps = Math.pow(10, -1*scale);
        if((twoPi.compareTo(width)<=0) ||
                        ((pi.compareTo(width)<eps) && (width.compareTo(twoPi)<eps) && (sinTemp == 0))) {
                min = new BigDecimal(-1);
                max = BigDecimal.ONE;
        } else if((pi.compareTo(width)<=eps && width.compareTo(twoPi)<=eps && sinTemp > 0) || (width.compareTo(pi)<eps && cond3)) {
                min = ElementaryMath.sin(a.getLeftLimit(), scale, forLeft);
                max = BigDecimal.ONE;
        } else if((pi.compareTo(width)<=eps && width.compareTo(twoPi)<=eps && sinTemp < 0) || width.compareTo(pi)<eps && cond4) {
                min = new BigDecimal(-1);
                max = ElementaryMath.sin(a.getRightLimit(), scale, forRight);
        } else {
               min = ElementaryMath.sin(a.getLeftLimit(), scale, forLeft);
               max = ElementaryMath.sin(a.getRightLimit(), scale, forRight);
        }
        BigDecimal[] minmax = new BigDecimal[2];
        minmax[0] = min;
        minmax[1] = max;
        return minmax;
    }
    /**
     * Algorithm of a sin calculation via Tchebyshe's polynoms
     */
    public static IntervalArithmetic sin(IntervalArithmetic a){       
        BigDecimal XnLeft;
        BigDecimal XnRight;
        BigDecimal[] minmax = getSinMinMax(a, a.getScale());
        if (a.isProper()){
            XnLeft = minmax[0];
            XnRight = minmax[1];
        }else{
           XnLeft = minmax[1];
           XnRight = minmax[0];
        }
        IntervalArithmetic interval = (IntervalArithmetic)a.clone();
        interval.setLimits(XnLeft,XnRight);
        return interval;
    }

    public static IntervalArithmetic sin(IntervalArithmetic a, int scale){
        BigDecimal XnLeft;
        BigDecimal XnRight;
        BigDecimal[] minmax = getSinMinMax(a, scale);
        if (a.isProper()){
            XnLeft = minmax[0];
            XnRight = minmax[1];
        }else{
           XnLeft = minmax[1];
           XnRight = minmax[0];
        }
        IntervalArithmetic interval = (IntervalArithmetic)a.clone();
        interval.setLimits(XnLeft,XnRight);
        return interval;
    }

    /**
     * Algorithm of a cos calculation via sin value
     */
    public static IntervalArithmetic cos(IntervalArithmetic a){
        BigDecimal two = new BigDecimal(2);
        BigDecimal halfPi = PI.divide(two, a.getScale());
        IntervalArithmetic interval = (IntervalArithmetic)a.clone();
        interval.setLimits(halfPi,halfPi);
        return sin(a.add(interval));
    }

    public static IntervalArithmetic cos(IntervalArithmetic a, int scale){
        BigDecimal two = new BigDecimal(2);
        BigDecimal halfPi = PI.divide(two, scale);
        IntervalArithmetic interval = (IntervalArithmetic)a.clone();
        interval.setLimits(halfPi,halfPi);
        return sin(a.add(interval), scale);
    }

    /**
     * Algorithm of a ln calculation via Taylor's series
     */
    public static IntervalArithmetic ln(IntervalArithmetic a){
        RoundingMode forLeft = a.getProperLeftRoundingMode();
        RoundingMode forRight = a.getProperRightRoundingMode();
        if (!a.isProper()) {
            forLeft = a.getImproperLeftRoundingMode();
            forRight = a.getImproperRightRoundingMode();
        }
        BigDecimal XnLeft = ElementaryMath.ln(a.getLeftLimit(), a.getScale(), forLeft);
        BigDecimal XnRight = ElementaryMath.ln(a.getRightLimit(), a.getScale(), forRight);
        IntervalArithmetic interval = (IntervalArithmetic)a.clone();
        interval.setLimits(XnLeft,XnRight);
        return interval;
    }

    public static IntervalArithmetic ln(IntervalArithmetic a, int scale){
        RoundingMode forLeft = a.getProperLeftRoundingMode();
        RoundingMode forRight = a.getProperRightRoundingMode();
        if (!a.isProper()) {
            forLeft = a.getImproperLeftRoundingMode();
            forRight = a.getImproperRightRoundingMode();
        }
        BigDecimal XnLeft = ElementaryMath.ln(a.getLeftLimit(), scale, forLeft);
        BigDecimal XnRight = ElementaryMath.ln(a.getRightLimit(), scale, forRight);
        IntervalArithmetic interval = (IntervalArithmetic)a.clone();
        interval.setLimits(XnLeft,XnRight, scale);
        return interval;
    }
}
